﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Effects.Wave;
using Effects.GUI;

namespace Effects
{
    // Class provides data source for EffectStreamPlayer. It's not actually stream.
    public class EffectStream
    {
        // instance of Wave to be effected and buffered
        private Wave.Wave wav;

        // EffectBoard to run signal through
        private EffectBoard effects;

        // read index to wav
        private int readPosition;

        // Property for current play position
        public int Position
        {
            get { return readPosition; }
        }

        // Porperty for lenght of the played Wave
        public int Length
        {
            get { return Convert.ToInt32(wav.SampleLength); }
        }

        // List already stores buffered data
        private List<double> buffered;

        // Timer that ensures enough samples are bufferd
        private System.Timers.Timer timer;

        // worksize for effectBoard
        private int worksize;

        // size of buffer
        private int bufferSize;

        // locker when adding to and deleting from buffer (buffered)
        private object locker;

        // Property to acces FmtChunk
        public FmtChunk FormatChunk
        {
            get { return wav.FmtChunk; }
        }

        public EffectStream(Wave.Wave wav,EffectBoard effects,int timerInterval,int bufferSize)
        {
            locker = new object();
            
            this.wav = wav;
            readPosition = 0;

            this.effects = effects;
            //buffered = new List<double>();
            this.worksize = 128;

            this.effects.SetDataSize(worksize);
            this.effects.SetSampleRate(wav.FmtChunk.SampleRate);
            
            //this.bufferSize = bufferSize/wav.FmtChunk.FrameSize;
            // set buffer length to be equal to one 4th of second
            this.bufferSize = Convert.ToInt32(wav.FmtChunk.SampleRate / 5);
            //prepares timer
            timer = new System.Timers.Timer(timerInterval);
            timer.Elapsed += new System.Timers.ElapsedEventHandler(this.TimerElapsed);

            Stop();
            //Buffer(bufferSize);
            //FillBuffer();

            
            //Start();
        }

        // stores how many samples are needed to be bufferd at the moment
        private int toBuffer;

        // deprecated
        public void Buffer(int bytes)
        {
            toBuffer += bytes / wav.FmtChunk.FrameSize;
        }

        // sets play position
        public void SetPosition(int position)
        {
            if (position<0 || position > Length)
                throw new ArgumentException("Position out of range. Given value must be greater than 0 and less than Length");
            // set new position and buffer
            this.readPosition = position;
            lock(locker)
            {
                buffered = new List<double>();
                toBuffer = bufferSize;
                FillBuffer();
            }
        }

        // method retrieves effected samples from buffer
        public byte[] Get(int bytes)
        {
            //Console.WriteLine("Get");
            double[] retData=null;

            // locks locker and gets data from buffer
            lock (locker)
            {
                retData = buffered.GetRange(0, bytes / wav.FmtChunk.FrameSize).ToArray();
                buffered.RemoveRange(0, bytes / wav.FmtChunk.FrameSize);
            }

            // tells the stream that more data need to be buffered
            toBuffer += bytes/wav.FmtChunk.FrameSize;
            
            // convert to byte and return
            return ToByteArray(retData);
            
            /*
            DateTime start = DateTime.Now;
            byte[] retData=new byte[bytes];
            Array.Copy(wav.DataChunk.Data,readPosition,retData,0,bytes);

            readPosition+=bytes;
            //Console.WriteLine(readPosition);
            DateTime end = DateTime.Now;
            TimeSpan dur = end - start;
            Console.WriteLine("Bytes: {0} -- ticks {1}", bytes, dur.Ticks);
             */
            //return retData;
        }

        // converts double value to byte
        private byte toByte(double d)
        {
            byte ret = Convert.ToByte(128 + 127 * d);
            return ret;
        }

        // converts double array to byte array according to wav.FmtChunk, som returned array can be played or writen to file as is
        private byte[] ToByteArray(double[] data)
        {
            byte[] retData = new byte[data.Length * wav.FmtChunk.FrameSize];
            int index = 0;

            int maxValue = 1;
            for (int i = 0; i < wav.FmtChunk.BitsPerSample / 8; i++)
                maxValue *= 256;
            maxValue /= 2;

            for (int i = 0; i < data.Length; i++)
            {
                if (maxValue == 128)
                {
                    retData[index++] = toByte(data[i]);
                }
                else
                {
                    int tmp = (int)(data[i] * maxValue);
                    byte[] buffer = BitConverter.GetBytes(tmp);

                    if (!BitConverter.IsLittleEndian)
                        Array.Reverse(buffer);

                    for (int j = 0; j < wav.FmtChunk.BitsPerSample / 8; j++)
                        retData[index++] = buffer[j];
                }
            }

            return retData;
        }

        // timer tick
        private void TimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            // check readPosition
            if (readPosition >= Length)
            {
                timer.Enabled = false;
                readPosition = Length;
            }
            else
            {
                // Fill buffer if needed
                FillBuffer();
            }
        }

        // Method that buffers another data
        private void FillBuffer()
        {
            if(readPosition<Length)
            {
               
                    while (toBuffer > worksize)
                    {
                        //Console.WriteLine("Stream buffering, to buffer: {0}, Read position: {1}",toBuffer,readPosition);
                        double[] buffer = new double[worksize];

                        for (int i = 0; i < worksize; i++)
                        {
                            try
                            {
                                buffer[i] = wav.getSample(1, readPosition++);
                            }
                            catch (ArgumentException)
                            {
                                buffer[i] = 0;
                            }
                        }

                        buffer = effects.Process(buffer);

                        lock (locker)
                        {
                            buffered.AddRange(buffer);
                        }

                        toBuffer -= worksize;
                    }
            }
        }

        public void Stop()
        {
            timer.Enabled = false;
            readPosition = 0;
            lock (locker)
            {
                buffered = new List<double>();
                
            }
            toBuffer = bufferSize;
            FillBuffer();

            
        }

        public void Start()
        {
            timer.Enabled = true;
        }

        public void Pause()
        {
            timer.Enabled = false;
        }

        public void Reset()
        {
            timer.Enabled = false;
            readPosition = 0;
            lock (locker)
            {
                buffered = new List<double>();
            }
            toBuffer = bufferSize;
            FillBuffer();
            timer.Enabled = true;

        }
    }
}
